<template>
  <div class="fx-popover" @click.stop="popoverClick"
       @mouseenter="handleHover($event,'enter')" @mouseleave="handleHover($event,'leave')">
    <div class="fx-popover-reference" ref="popoverReferenceDom">
      <slot name="reference"/>
    </div>
    <input class="fx-popover-hide" type="text" ref="popoverInputDom" @focus="popoverFocus">
    <transition @before-enter="popoverBeforeEnter($event)"
                @enter="popoverEnter($event)"
                @after-enter="$emit('afterShow')"
                @before-leave="popoverBeforeEnter($event,true)"
                @leave="popoverEnter($event,true)"
                @after-leave="$emit('afterHide')">
      <div class="fx-popover-visible" ref="popoverVisibleDom" :style="popoverStyle" v-if="visible&&!disabled">
        <slot>
          <div class="fx-popover-title" v-if="title">
            <i class="iconfont fs20 mr5" v-if="type" :class="`icon-${type}`"/>
            {{ title }}
          </div>
          <div class="fx-popover-content" v-if="content">{{ content }}</div>
        </slot>
        <div class="fx-popover-tip" :class="position"></div>
        <div class="fx-popover-footer mt10" v-if="isConfirm">
          <fx-button size="mini" class="mr5" v-if="cancelBtn" @click="$emit('confirm','cancel')">
            {{ cancelText }}
          </fx-button>
          <fx-button size="mini" type="primary" @click="$emit('confirm','define')">
            {{ defineText }}
          </fx-button>
        </div>
      </div>
    </transition>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, onMounted, PropType, ref, SetupContext, watch } from 'vue'
import { getZIndex, useClickBlank } from '@/base/hooks'
import { prefixStyle } from '@/base/utils'
import { codeRule, ComponentTypes, componentTypesArr, PositionPath } from '@/base/types'
import FxButton from '../button/button.vue'

type PopoverTrigger = 'hover' | 'click' | 'focus'
type PopoverPosition = PositionPath

interface PopoverProps {
  position: PopoverPosition;
  trigger: PopoverTrigger;
  title: string;
  content: string;
  width: string;
  disabled: boolean;
  type: ComponentTypes;
  isConfirm: boolean;
  cancelBtn: boolean;
  cancelText: string;
  defineText: string;
}

export default defineComponent({
  name: 'popover',
  components: {
    FxButton
  },
  props: {
    // 方向
    position: {
      type: String as PropType<PopoverPosition>,
      default: 'rightTop'
    },
    // 标题
    title: {
      type: String as PropType<string>,
      default: ''
    },
    // 内容
    content: {
      type: String as PropType<string>,
      default: ''
    },
    // 宽度
    width: {
      type: String as PropType<string>,
      default: '120px'
    },
    // 触发方式
    trigger: {
      type: String as PropType<PopoverTrigger>,
      default: 'click'
    },
    // 禁用
    disabled: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    // 类型
    type: {
      type: String as PropType<ComponentTypes>,
      default: null,
      validator (val: ComponentTypes) {
        return componentTypesArr.includes(val)
      }
    },
    // 按钮样式
    isConfirm: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    // 取消按钮
    cancelBtn: {
      type: Boolean as PropType<boolean>,
      default: true
    },
    // 取消按钮文字
    cancelText: {
      type: String as PropType<string>,
      default: '取消'
    },
    // 确定按钮文字
    defineText: {
      type: String as PropType<string>,
      default: '确定'
    }
  },
  emits: ['show', 'afterShow', 'hide', 'afterHide', 'confirm'],
  setup (props: PopoverProps, ctx: SetupContext) {
    let popoverTimer: null | ReturnType<typeof setTimeout> = null
    const visible = ref<boolean>(false)
    const popoverReferenceDom = ref<HTMLElement>()
    const popoverInputDom = ref<HTMLElement>()
    const popoverVisibleDom = ref<HTMLElement>()
    useClickBlank([popoverReferenceDom, popoverVisibleDom], visible)

    watch(visible, (val: boolean) => {
      if (!val) document.removeEventListener('scroll', handleScroll, true)
      else document.addEventListener('scroll', handleScroll, true)
    })

    const popoverStyle = computed(() => {
      const isCode = codeRule.test(props.width)
      return {
        width: isCode ? props.width : `${props.width}px`
      }
    })

    // 初始化弹窗层位置
    const initPopoverPosition = (el: HTMLElement): void => {
      if (!popoverReferenceDom.value) return
      const rect = popoverReferenceDom.value?.getBoundingClientRect()
      el.style.zIndex = String(getZIndex())
      const padding = 20
      const onTop = rect.top - el.clientHeight - padding
      const onBottom = rect.top + rect.height + padding
      const toLeft = rect.left - padding - el.clientWidth
      const onRight = rect.left + rect.width - el.clientWidth
      const toRight = rect.left + rect.width + padding
      const topBottom = rect.top + rect.height - el.clientHeight
      const topCenter = rect.top + rect.height / 2 - el.clientHeight / 2
      const leftCenter = rect.left + rect.width / 2 - el.clientWidth / 2
      switch (props.position) {
        case 'topLeft':
          el.style.left = `${rect.left}px`
          el.style.top = `${onTop}px`
          break
        case 'top':
          el.style.left = `${leftCenter}px`
          el.style.top = `${onTop}px`
          break
        case 'topRight':
          el.style.left = `${onRight}px`
          el.style.top = `${onTop}px`
          break
        case 'rightTop':
          el.style.left = `${toRight}px`
          el.style.top = `${rect.top}px`
          break
        case 'right':
          el.style.left = `${toRight}px`
          el.style.top = `${topCenter}px`
          break
        case 'rightBottom':
          el.style.left = `${toRight}px`
          el.style.top = `${topBottom}px`
          break
        case 'bottomRight':
          el.style.left = `${onRight}px`
          el.style.top = `${onBottom}px`
          break
        case 'bottom':
          el.style.left = `${leftCenter}px`
          el.style.top = `${onBottom}px`
          break
        case 'bottomLeft':
          el.style.left = `${rect.left}px`
          el.style.top = `${onBottom}px`
          break
        case 'leftBottom':
          el.style.left = `${toLeft}px`
          el.style.top = `${topBottom}px`
          break
        case 'left':
          el.style.left = `${toLeft}px`
          el.style.top = `${topCenter}px`
          break
        case 'leftTop':
          el.style.left = `${toLeft}px`
          el.style.top = `${rect.top}px`
          break
        default:
          el.style.left = `${toRight}px`
          el.style.top = `${topCenter}px`
          break
      }
    }

    // 弹出动画
    const popoverBeforeEnter = (el: HTMLElement, isLeave = false): void => {
      isLeave ? ctx.emit('hide') : ctx.emit('show')
      const Transform = prefixStyle('transform') as number
      const TransformOrigin = prefixStyle('transformOrigin') as number
      const top = /top/.test(props.position)
      const bottom = /bottom/.test(props.position)
      const left = /left/.test(props.position)
      const right = /right/.test(props.position)
      if (top || bottom) {
        el.style[Transform] = `scaleX(1) scaleY(${isLeave ? 1 : 0})`
        el.style[TransformOrigin] = top ? 'bottom center' : 'top center'
      }
      if (left || right) {
        el.style[Transform] = `scaleX(${isLeave ? 1 : 0}) scaleY(1)`
        el.style[TransformOrigin] = left ? 'right center' : 'left center'
      }
    }

    // 弹出动画
    const popoverEnter = (el: HTMLElement, isLeave = false): void => {
      initPopoverPosition(el)
      const Transform = prefixStyle('transform') as number
      const top = /top/.test(props.position)
      const bottom = /bottom/.test(props.position)
      const left = /left/.test(props.position)
      const right = /right/.test(props.position)
      if (top || bottom) el.style[Transform] = `scaleX(1) scaleY(${isLeave ? 0 : 1})`
      if (left || right) el.style[Transform] = `scaleX(${isLeave ? 0 : 1}) scaleY(1)`
    }

    // 点击事件
    const popoverClick = (): void => {
      if (props.disabled) return
      if (props.trigger === 'click') visible.value = !visible.value
      if (props.trigger === 'focus' && popoverInputDom.value) {
        popoverInputDom.value.focus()
      }
    }

    // hover事件
    const handleHover = (ev: MouseEvent, hover: 'enter' | 'leave'): void => {
      if (props.disabled) return
      if (props.trigger === 'hover') {
        popoverTimer && clearTimeout(popoverTimer)
        popoverTimer = setTimeout(() => {
          visible.value = hover === 'enter'
        }, 100)
      }
    }

    // 聚焦
    const popoverFocus = () => {
      if (props.disabled) return
      if (props.trigger === 'focus') visible.value = true
    }

    const handleScroll = () => {
      if (!popoverVisibleDom.value) return
      initPopoverPosition(popoverVisibleDom.value)
    }

    return {
      visible,
      popoverReferenceDom,
      popoverInputDom,
      popoverVisibleDom,
      popoverStyle,
      popoverBeforeEnter,
      popoverEnter,
      popoverClick,
      handleHover,
      popoverFocus
    }
  }
})
</script>
